package com.bjy.qa.agent.response;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.rslai.commons.validator.jsonvalidator.JsonValidator;
import org.apache.commons.lang3.StringUtils;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.DifferenceListener;
import org.custommonkey.xmlunit.XMLUnit;
import org.hamcrest.Matchers;
import org.hamcrest.core.Is;
import org.hamcrest.core.StringContains;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.hamcrest.Matchers.matchesRegex;
import static org.hamcrest.Matchers.not;

public class Response {
    private static final Logger logger = LoggerFactory.getLogger(Response.class);

    private static final Pattern ASSERT_CONTAINS_PATTERN = Pattern.compile("^containsStr\\([\",\'](.*?)[\",\']\\)$"); // 字符串包含对比
    private static final Pattern ASSERT_NOT_CONTAINS_PATTERN = Pattern.compile("^notContainsStr\\([\",\'](.*?)[\",\']\\)$"); // 字符不串包含对比
    private static final Pattern ASSERT_REGEX_PATTERN = Pattern.compile("^regex\\([\",\'](.*?)[\",\']\\)$"); // 字符串正则表达式对比

    Object body; // 响应体
    Throwable exception; // 异常信息

    /**
     * 构造函数
     */
    public Response() {
    }

    /**
     * 构造函数
     * @param body 响应体
     * @param exception 异常信息
     */
    public Response(Object body, Throwable exception) {
        this.exception = exception;
        this.body = body;
    }

    /**
     * 验证响应
     * @param expected 预期结果
     */
    public void verify(Map<String, String> expected) {
        assertBody((String) expected.get("body"));
        assertException(expected);
        assertXml((String) expected.get("xml"));
    }

    /**
     * xml 格式验证
     * @param expected
     */
    private void assertXml(String expected) {
        if (StringUtils.isNotBlank(expected))
            try {
                XMLUnit.setIgnoreWhitespace(true);
                XMLUnit.setIgnoreAttributeOrder(true);
                XMLUnit.setIgnoreComments(true);
                XMLUnit.setIgnoreDiffBetweenTextAndCDATA(true);
                Diff diff = new Diff(expected, this.body.toString());
                List differents = new ArrayList();
                DifferenceListener differentListener = new IgnoreUndefinedNodeDiffListener(differents);
                diff.overrideDifferenceListener(differentListener);
                boolean similar = diff.similar();
                Assert.assertTrue("Xml逻辑是不相等：" + differents.toString(), similar);
            } catch (SAXException e) {
                logger.error(e.getMessage(), e);
                throw new AssertionError(e);
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                throw new AssertionError(e);
            }
    }

    /**
     * 验证 body
     * @param expected
     */
    private void assertBody(String expected) {
        if (expected != null) {
            String json = bodyInString(this.body);
            if (isJson(json)) {
                JsonValidator.validate(expected, json);
            } else {
                assertString(json, expected);
            }
        }
    }

    /**
     * 字符串 格式验证
     * @param actual 实际值
     * @param expected 期望值
     */
    private void assertString(String actual, String expected) {
        actual = StringUtils.trim(actual);
        expected = StringUtils.trim(expected);

        // 字符串包含对比
        Matcher matcher = ASSERT_CONTAINS_PATTERN.matcher(expected);
        if (matcher.find()) {
            expected = matcher.group(1);
            Assert.assertThat("字符串包含对比失败", actual, StringContains.containsString(expected));
            return;
        }

        // 字符串不包含对比
        matcher = ASSERT_NOT_CONTAINS_PATTERN.matcher(expected);
        if (matcher.find()) {
            expected = matcher.group(1);
            Assert.assertThat("字符串不包含对比失败", actual, not(StringContains.containsString(expected)));
            return;
        }

        // 字符串正则表达式对比
        matcher = ASSERT_REGEX_PATTERN.matcher(expected);
        if (matcher.find()) {
            expected = matcher.group(1);
            Assert.assertThat("字符串正则表达式对比失败", actual, matchesRegex(Pattern.compile(expected, Pattern.DOTALL))); // Pattern.DOTALL 使 . 可以匹配换行符
            return;
        }

        // 字符串相等对比
        try {
            Assert.assertThat("字符串相同对比失败", actual, Is.is(expected));
        } catch (AssertionError e) {
            Assert.assertThat("字符串相同对比失败", String.format("\"%s\"", new Object[]{actual}), Is.is(expected));
        }
    }

    private boolean isJson(String json) {
        try {
            JSON.parse(json);
            return true;
        } catch (Exception e) {
        }
        return false;
    }

    /**
     * 响应体转换为 Json 字符串
     * @param body
     * @return
     */
    private String bodyInString(Object body) {
        if ((body instanceof String))
            return body.toString();
        return responseJson(body);
    }

    /**
     * 响应体转换为 json
     * @param body
     * @return
     */
    private String responseJson(Object body) {
        // TODO: 2023/5/2 这里重写了 double 的序列化，有可能不需要，需要再处理
//        Boolean jsonWriteOriginalDoubleValue = Boolean.valueOf(PropertyUtils.getProperty("json_write_original_double_value", "false"));
//        SerializeConfig config = new SerializeConfig();
//        if (jsonWriteOriginalDoubleValue.booleanValue()) {
//            config.setAsmEnable(false);
//            config.put(Double.class, QunitDoubleSerializer.INSTANCE);
//        }
//        return JSON.toJSONString(body, config, new SerializerFeature[]{SerializerFeature.WriteMapNullValue});
        return JSON.toJSONString(body, new SerializerFeature[]{SerializerFeature.WriteMapNullValue});
    }

    /**
     * 异常 验证
     * @param expected
     */
    private void assertException(Map<String, String> expected) {
        String exceptionClassName = (String) expected.get("class");
        String exceptionMessage = (String) expected.get("message");

        String actualMessage = "";
        if (this.exception != null) {
            actualMessage = this.exception.getMessage();
        }
        if (StringUtils.isNotBlank(exceptionClassName)) {
            Class expectedException = getClass(exceptionClassName);
            Assert.assertThat("实际抛出的异常的类型和期望的异常类型不同", this.exception, Matchers.instanceOf(expectedException));
        }

        if (StringUtils.isNotBlank(exceptionMessage))
            Assert.assertThat("实际抛出异常的message和期望的异常message不同", actualMessage, StringContains.containsString(exceptionMessage));
    }

    private Class<?> getClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            logger.error(String.format("class <%s> not found.", new Object[]{className}), e);
            throw new AssertionError(e);
        }
    }

    public void setBody(Object body) {
        this.body = body;
    }

    public Object getBody() {
        return this.body;
    }

    public void setException(Throwable exception) {
        this.exception = exception;
    }

    public Throwable getException() {
        return this.exception;
    }

    @Override
    public String toString() {
        return "Response{" +
                "body=" + body +
                ", exception=" + exception +
                '}';
    }
}
